﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Concat
{
    class Parser
    {
        public Scanner scanner;
        public Token cur;

        class ParsingWord
        {
            public Predicate<Token> parse;
            public Func<Token, Parser, Term[]> parser;

            public ParsingWord(Predicate<Token> parse, Func<Token, Parser, Term[]> parser)
            {
                this.parse = parse;
                this.parser = parser;
            }
        }
        List<ParsingWord> parsingWords = new List<ParsingWord>();

        public Parser()
        {
            addParsingWord((token) => token.text.EndsWith(">>"), (token, parser) =>
            {
                // obj {ident}>> ==>> obj :{ident} get-slot
                return new Term[] {
                    new SymTerm(token.text.Remove(token.text.Length-2), token.loc), 
                    new IdentTerm("get-slot", Location.External) 
                };
            });
            addParsingWord((token) => token.text.StartsWith(">>"), (token, parser) =>
            {
                // obj val >>{ident} ==>> obj val :{ident} set-slot
                return new Term[] {
                    new SymTerm(token.text.Substring(2), 
                        new Location(token.loc.file, token.loc.pos + 2,
                                     token.loc.line,token.loc.col + 2)),
                    new IdentTerm("set-slot", Location.External)
                };
            });
            addParsingWord((token) => token.text.StartsWith("%"), (token, parser) =>
            {
                // obj args %{ident} ==> obj args :{ident} send
                return new Term[] {
                    new SymTerm(token.text.Substring(1),
                        new Location(token.loc.file,token.loc.pos+1,
                            token.loc.line,token.loc.col+1)),
                    new IdentTerm("send", Location.External)
                };
            });
            addParsingWord((token) => token.text == "{", (token, parser) =>
            {
                // { (1) (2) (3) ... } ==>> nil (1) append (2) append (3) append ...
                List<Term> listBuilderTerms = new List<Term>();
                List<Term> listItems = new List<Term>();
                while (true)
                {
                    if (parser.cur.text == "}")
                    {
                        parser.next();
                        break;
                    }
                    Term cur = parser.parse();
                    listItems.Add(cur);
                }
                QuoteTerm quote = new QuoteTerm(token.loc, new string[0], listItems.ToArray());
                return new Term[] {
                    quote,
                    new IdentTerm("list", Location.External)
                };
            });
        }
        public Parser(Scanner scanner)
            : this()
        {
            setScanner(scanner);
        }
        public void setScanner(Scanner scanner)
        {
            this.scanner = scanner;
            next();
        }

        public void addParsingWord(Predicate<Token> parse, Func<Token, Parser, Term[]> parser)
        {
            parsingWords.Add(new ParsingWord(parse, parser));
        }
        public void addConcatParsingWord(Interpreter interpreter, Quote parse, Quote parsing)
        {
            // macro [(token) "{" token text>> =] [token { 2 } :substring send to-sym quote [set-slot] compose]
            addParsingWord(token =>
            {
                interpreter.push(token);
                interpreter.exec(parse, false).waitEvent(() => interpreter.forceRunCalls());
                return interpreter.pop<bool>();
            }, (token, parser) =>
            {
                interpreter.push(parser);
                interpreter.push(token);
                interpreter.exec(parsing, false).waitEvent(() => interpreter.forceRunCalls());
                return interpreter.pop<Quote>().terms;
            });
        }

        [System.Diagnostics.DebuggerNonUserCode]
        public void next()
        {
            cur = scanner.getToken();
        }
        [System.Diagnostics.DebuggerNonUserCode]
        T next<T>(Func<Token, T> gen)
        {
            Token copy = cur;
            next();
            return gen(copy);
        }

        class MacroTerm : Term
        {
            public Term[] terms;

            public MacroTerm(params Term[] terms)
                : base(null)
            {
                this.terms = terms;
            }

            public override HashSet<string> closureVars(HashSet<string> vars)
            {
                throw new NotImplementedException("Should never have a MacroTerm in public...");
            }
        }

        // returns null when at EOF
        Term parse()
        {
            foreach (ParsingWord parsingWord in parsingWords)
            {
                if (parsingWord.parse(cur))
                {
                    Token copyCur = cur;
                    next();
                    return new MacroTerm(parsingWord.parser(copyCur, this));
                }
            }

            if (cur is EOFToken)
                return null;
            else if (cur is IntToken)
                return next(copy => new IntTerm(((IntToken)copy).value, copy.loc));
            else if (cur is NumToken)
                return next(copy => new NumTerm(((NumToken)copy).value, copy.loc));
            else if (cur is StrToken)
                return next(copy => new StrTerm(copy.text, ((StrToken)copy).data, copy.loc));
            else if (cur is SymToken)
                return next(copy => new SymTerm(((SymToken)copy).symbol, copy.loc));
            else if (cur.GetType() == typeof(IdentToken)) // make sure it is not a derived token type
                return next(copy => new IdentTerm(((IdentToken)copy).ident, copy.loc));
            else if (cur is LeftBracketToken)
                return parseQuote(cur, true);
            else if (cur is DefToken)
                return parseDef();
            else if (cur is StructToken)
                return parseStruct();
            else if (cur is ExtendStructToken)
                return parseExtendStruct();
            else if (cur is DefMethodToken)
                return parseDefMethod();
            else if (cur is ClassToken)
                return parseClass();
            else if (cur is ExtendClassToken)
                return parseExtendClass();
            else if (cur is ClassMemberToken)
                return parseClassMember();
            else // LeftParen, RightParen
                throw new InvalidTokenException(cur);
        }
        QuoteTerm parseQuote(Token bracket, bool allowFormals)
        {
            next();
            string[] formals = allowFormals ? parseFormals() : new string[0];
            List<Term> words = new List<Term>();
            while (!(cur is RightBracketToken || cur is EOFToken))
                words.Add(parse());
            if (cur is EOFToken)
                throw new MismatchedBracketException((LeftBracketToken)bracket);
            next();
            return new QuoteTerm(bracket.loc, formals, words.ToArray());
        }
        DefTerm parseDef()
        {
            Location loc = cur.loc;
            next();
            string[] formals = parseFormals();
            if (!(cur is IdentToken))
                throw new MissingNameException(cur);
            string name = ((IdentToken)cur).ident;
            next();
            if (!(cur is LeftBracketToken))
                throw new MissingBodyException(cur);
            return new DefTerm(formals.ToArray(), name, loc, parseQuote(cur, false));
        }
        StructTerm parseStruct()
        {
            Location loc = cur.loc;
            next();

            if (!(cur is IdentToken))
                throw new MissingNameException(cur);
            string name = ((IdentToken)cur).ident;
            next();

            if (cur.text != "{")
                throw new ExpectedCurlyBracketException(cur);
            next();

            List<string> slots = new List<string>();
            while (cur.text != "}")
            {
                if (!(cur is IdentToken))
                    throw new InvalidTokenException(cur);
                string slot = ((IdentToken)cur).ident;
                if (slots.Contains(slot))
                    throw new DuplicateSlotException(slot, cur);
                slots.Add(slot);
                next();
            }
            next();

            return new StructTerm(loc, name, slots.ToArray());
        }
        ExtendStructTerm parseExtendStruct()
        {
            Location loc = cur.loc;
            next();

            if (!(cur is IdentToken))
                throw new MissingNameException(cur);
            string parent = ((IdentToken)cur).ident;
            next(); 
            if (!(cur is IdentToken))
                throw new MissingNameException(cur);
            string name = ((IdentToken)cur).ident;
            next();

            if (cur.text != "{")
                throw new ExpectedCurlyBracketException(cur);
            next();

            List<string> slots = new List<string>();
            while (cur.text != "}")
            {
                if (!(cur is IdentToken))
                    throw new InvalidTokenException(cur);
                string slot = ((IdentToken)cur).ident;
                if (slots.Contains(slot))
                    throw new DuplicateSlotException(slot, cur);
                slots.Add(slot);
                next();
            }
            next();

            return new ExtendStructTerm(loc, parent, name, slots.ToArray());
        }
        DefMethodTerm parseDefMethod()
        {
            // method <<formals>> { param type } { param type } ... name <<QuoteTerm>>
            Location loc = cur.loc;
            next();
            return parseMethod(loc, true);
        }
        ClassTerm parseClass()
        {
            // class Name { <<method>> <<method>> ... }
            Location loc = cur.loc;
            next();
            if (!(cur is IdentToken))
                throw new MissingNameException(cur);
            string name = ((IdentToken)cur).ident;
            next();
            if (cur.text != "{")
                throw new ExpectedCurlyBracketException(cur);
            next();
            List<DefMethodTerm> methods = new List<DefMethodTerm>();
            while (cur.text != "}")
                methods.Add(parseMethod(cur.loc, false));
            next();
            return new ClassTerm(loc, name, methods.ToArray());
        }
        ExtendClassTerm parseExtendClass()
        {
            // extend-class parent name { <<method>> <<method>> ... }
            Location loc = cur.loc;
            next();
            if (!(cur is IdentToken))
                throw new MissingNameException(cur);
            string parent = ((IdentToken)cur).ident;
            next();
            if (!(cur is IdentToken))
                throw new MissingNameException(cur);
            string name = ((IdentToken)cur).ident;
            next();
            if (cur.text != "{")
                throw new ExpectedCurlyBracketException(cur);
            next();
            List<DefMethodTerm> methods = new List<DefMethodTerm>();
            while (cur.text != "}")
                methods.Add(parseMethod(cur.loc, false));
            next();
            return new ExtendClassTerm(loc, parent, name, methods.ToArray());
        }
        ClassMemberTerm parseClassMember()
        {
            // class-member class member
            // class-member class { member member ... }
            Location loc = cur.loc;
            next();
            if (!(cur is IdentToken))
                throw new MissingNameException(cur);
            string className = ((IdentToken)cur).ident;
            next();
            List<string> members = new List<string>();
            if (cur.text == "{")
            {
                next();
                while (cur.text != "}")
                {
                    members.Add(cur.text);
                    next();
                }
            }
            else
                members.Add(cur.text);
            next();
            return new ClassMemberTerm(loc, className, members.ToArray());
        }

        DefMethodTerm parseMethod(Location loc, bool hasQuote)
        {
            string[] formals = parseFormals();
            List<string> paramNames = new List<string>(),
                         typeNames = new List<string>();
            while (cur.text == "{")
            {
                next();
                if (!(cur is IdentToken))
                    throw new MissingNameException(cur);
                string paramName = ((IdentToken)cur).ident;
                if (paramName != "_" && paramNames.Contains(paramName))
                    throw new DuplicateParamException(paramName, cur);
                paramNames.Add(paramName);

                next();
                if (!(cur is IdentToken))
                    throw new MissingNameException(cur);
                string typeName = ((IdentToken)cur).ident;
                typeNames.Add(typeName);

                next();
                if (cur.text != "}")
                    throw new ExpectedCurlyBracketException(cur);
                next();
            }

            if (!(cur is IdentToken))
                throw new MissingNameException(cur);
            string name = ((IdentToken)cur).ident;

            next();
            if (hasQuote)
            {
                if (!(cur is LeftBracketToken))
                    throw new MissingBodyException(cur);
                return new DefMethodTerm(formals, typeNames.ToArray(), paramNames.ToArray(), name, loc, parseQuote(cur, false));
            }
            else
            {
                return new DefMethodTerm(formals, typeNames.ToArray(), paramNames.ToArray(), name, null, loc, null);
            }
        }

        string[] parseFormals()
        {
            List<string> formals = new List<string>();
            if (cur is LeftParenToken)
            {
                next();
                while (!(cur is RightParenToken))
                {
                    if (cur is IdentToken)
                    {
                        string formal = ((IdentToken)cur).ident;
                        if (formals.Contains(formal))
                            throw new DuplicateFormalException(formal, cur);
                        formals.Add(formal);
                    }
                    else
                        throw new InvalidTokenException(cur);
                    next();
                }
                next();
            }
            return formals.ToArray();
        }

        public Term[] parseAll()
        {
            List<Term> words = new List<Term>();
            while (true)
            {
                Term cur = this.parse();
                if (cur == null)
                    return words.ToArray();
                expandMacros(words, cur);
            }
        }
        void expandMacros(List<Term> words, Term term)
        {
            if (term is MacroTerm)
            {
                foreach (Term macroTerm in ((MacroTerm)term).terms)
                    expandMacros(words, macroTerm);
            }
            else if (term is QuoteTerm)
            {
                QuoteTerm quoteTerm = (QuoteTerm)term;
                List<Term> quoteTerms = new List<Term>();
                foreach (Term bodyTerm in quoteTerm.terms)
                    expandMacros(quoteTerms, bodyTerm);
                words.Add(new QuoteTerm(quoteTerm.loc, quoteTerm.formals, quoteTerms.ToArray()));
            }
            else if (term is DefTerm)
            {
                DefTerm defTerm = (DefTerm)term;
                List<Term> bodyTerms = new List<Term>();
                foreach (Term bodyTerm in defTerm.def)
                    expandMacros(bodyTerms, bodyTerm);
                words.Add(new DefTerm(defTerm.formals, defTerm.name, defTerm.bodyLoc, defTerm.loc, 
                    bodyTerms.ToArray()));
            }
            else if (term is DefMethodTerm)
            {
                DefMethodTerm defMethodTerm = (DefMethodTerm)term;
                List<Term> bodyTerms = new List<Term>();
                foreach (Term bodyTerm in defMethodTerm.def)
                    expandMacros(bodyTerms, bodyTerm);
                words.Add(new DefMethodTerm(defMethodTerm.formals, defMethodTerm.types, 
                    defMethodTerm.paramNames, defMethodTerm.name,
                    defMethodTerm.bodyLoc, defMethodTerm.loc,
                    bodyTerms.ToArray()));
            }
            else words.Add(term);
        }
    }
}
